#!/bin/bash
set -euo pipefail

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

REPO_ROOT_PATH=$SCRIPTPATH/../
MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile

VERSION=$(make -s -f $MAKE_FILE_PATH version)
PLATFORMS=("linux/amd64")
MANIFEST_IMAGES=()
MANIFEST=""
DOCKER_CLI_CONFIG="$HOME/.docker/config.json"

USAGE=$(cat << 'EOM'
  Usage: push-docker-images  [-p <platform pairs>]
  Pushes docker images for the platform pairs passed in w/ a manifest list
  Example: push-docker-images -p "linux/amd64,linux/arm"
          Optional:
            -p          Platform pair list (os/architecture) [DEFAULT: linux/amd64]
            -r          IMAGE REPO: set the docker image repo
            -v          VERSION: The application version of the docker image [DEFAULT: output of `make version`]
            -m          Create a docker manifest
EOM
)

# Process our input arguments
while getopts "mp:r:v:" opt; do
  case ${opt} in
    p ) # Platform Pairs
        IFS=',' read -ra PLATFORMS <<< "$OPTARG"
      ;;
    r ) # Image Repo
        IMAGE_REPO="$OPTARG"
      ;;
    v ) # Image Version
        VERSION="$OPTARG"
      ;;
    m ) # Docker manifest
        MANIFEST="true"
      ;;
    \? )
        echo "$USAGE" 1>&2
        exit
      ;;
  esac
done

if [[ ${#PLATFORMS[@]} -gt 1 && $MANIFEST != "true" ]]; then
    echo "Only one platform can be pushed if you do not create a manifest."
    echo "Try again with the -m option"
    exit 1
fi

# Existing manifests cannot be updated only overwritten; therefore,
# if manifest exists already, fetch existing platforms so "updated" manifest includes images
# that were there previously
if [[ $MANIFEST == "true" ]]; then
  if [[ ! -f $DOCKER_CLI_CONFIG ]]; then
    echo '{"experimental":"enabled"}' > $DOCKER_CLI_CONFIG
    echo "Created docker config file"
  fi
  cat <<< "$(jq '.+{"experimental":"enabled"}' $DOCKER_CLI_CONFIG)" > $DOCKER_CLI_CONFIG
  echo "Enabled experimental CLI features to execute docker manifest commands"
  # Delete the local version of the manifest so we rely solely on the remote manifest
  docker manifest rm $IMAGE_REPO:$VERSION || :
  manifest_exists=$(docker manifest inspect $IMAGE_REPO:$VERSION > /dev/null ; echo $?)
  if [[ manifest_exists -eq 0 ]]; then
    echo "manifest already exists"
    EXISTING_IMAGES=()

    # Run while loop to collect images with no OS version (typically linux)
    while IFS='' read -r line; do 
      EXISTING_IMAGES+=("$line"); 
    done < <(docker manifest inspect $IMAGE_REPO:$VERSION | jq -r '.manifests[] | select(.platform."os.version" == null) | "\(.platform.os)-\(.platform.architecture)"')

    # Run while loop to collect images with OS version (typically windows)
    while IFS='' read -r line; do 
      EXISTING_IMAGES+=("$line"); 
    done < <(docker manifest inspect $IMAGE_REPO:$VERSION | jq -r '.manifests[] | select(.platform."os.version" != null) | "\(.platform.os)-\(.platform."os.version")-\(.platform.architecture)"')

    # treat separate from PLATFORMS because existing images don't need to be tagged and pushed
    for os_arch in "${EXISTING_IMAGES[@]}"; do
      img_tag="$IMAGE_REPO:$VERSION-$os_arch"
      MANIFEST_IMAGES+=("$img_tag")
    done
    echo "images already in manifest: ${MANIFEST_IMAGES[*]}"
  fi
fi

for os_arch in "${PLATFORMS[@]}"; do
    os=$(echo $os_arch | cut -d'/' -f1)
    arch=$(echo $os_arch | cut -d'/' -f2)

    img_tag_w_platform="$IMAGE_REPO:$VERSION-$os-$arch"

    if [[ $MANIFEST == "true" ]]; then
        img_tag=$img_tag_w_platform
    else
        img_tag="$IMAGE_REPO:$VERSION"
        docker tag $img_tag_w_platform $img_tag
    fi
    docker push $img_tag
    MANIFEST_IMAGES+=("$img_tag")
done

if [[ $MANIFEST == "true" ]]; then
    current_os=$(uname)
    # Windows will append '\r' to the end of $img which
    # results in docker failing to create the manifest due to invalid reference format.
    # However, MacOS does not recognize '\r' as carriage return
    # and attempts to remove literal 'r' chars; therefore, made this so portable
    for img in "${MANIFEST_IMAGES[@]}"; do
      if [[ $current_os == "Darwin" ]]; then
        updated_img=$img
      else
        updated_img=$(echo $img | sed -e 's/\r$//')
      fi
      echo "creating manifest for $updated_img"
      docker manifest create $IMAGE_REPO:$VERSION $updated_img --amend

      # Theoretically, this will not be necessary if we move all our builds to docker buildx.
      # (The Windows build is the only one not using it at the moment.) The manifest create --amend command
      # should figure out the OS and architecture automatically if the container was built properly.
      # However, our builds in the past required this explicit annotation, and it doesn't hurt to keep it for now.
      os_arch=$(echo ${updated_img//$IMAGE_REPO:$VERSION-/})
      os=$(echo $os_arch | cut -d'-' -f1)

      # os_arch may be linux-amd64 or windows-10.0.17763.6189-amd64. To get the proper architecture, the bash command
      # will extract the last element after the hyphen (-).
      arch=${os_arch##*-}

      echo "annotating manifest"
      docker manifest annotate $IMAGE_REPO:$VERSION $updated_img --arch $arch --os $os
    done

    echo "pushing manifest"
    docker manifest push --purge $IMAGE_REPO:$VERSION
fi
